#!/usr/bin/env perl

# 
# This does not really form part of the core utilities since it is
# particular to marketing database. If will compare two given
# directory trees, reporting on the differences between them.
#
# Also includes functinoality to ignore comments, which can be
# set using the --comments arguments, which can be used more than
# once if necessary, so typically might be:
# --comments "#" --comments "/* */"
#
# If the first case it will ignore complete lines that begin with "#",
# whilst in the 2nd case will ignore blocks that start/finish with /* .. */
#

use Digest::MD5;
use Getopt::Long;
use File::Basename;
use File::Find;
use Cwd;

$|=1;
$_PRG=basename($0);
$_SPC=" " x length($_PRG);
@comments=();

sub usage {
	print "
Usage: $_PRG [--diffs] [--comments 'start [finish]' ... ] dir1 [dir2]
       $_SPC [--onlysuffixes pattern ... | --match pattern]
       $_SPC [--nosummary]
";
	exit 1;
}

$ARG_diffs=0;
$ARG_nosummary=0;
@ARG_onlysuffixes=();
$ARG_match=undef;

if(!GetOptions(
	"nosummary"		=> \$ARG_nosummary,
	"diffs"			=> \$ARG_diffs,
	"match=s"		=> \$ARG_match,
	"onlysuffixes=s"	=> \@ARG_onlysuffixes,
	"comments=s"		=> \@comments,
	)) {
	usage;
}

if(! defined($ARGV[0])) {
	usage;
}
if(! -d $ARGV[0]) {
	print STDERR "Error: $ARGV[0] is not a valid directory!\n";
	exit 1;
}
if(! defined($ARGV[1])) {
	push @ARGV,cwd;
}
if(! -d $ARGV[1]) {
	print STDERR "Error: $ARGV[1] is not a valid directory!\n";
	exit 1;
}

@start_c=();
@end_c=();
for (@comments) {
	@F=split;
	push @start_c,$F[0];
	if(defined($F[1])) {
		push @end_c,$F[1];
	} else {
		push @end_c,"";
	}
}

sub mangle_comments($) {
my ($file)=@_;
my ($incomment,$cline,$ctype,$inquote,$fd,$l,$x,$cstuff,$i);

	my $data="";
	if(!open($fd,$file)) {
		print "Warning: Unable to open file \"$file\".\n";
		return "";
	}
	$incomment=$ctype=$inquote=0;
	while(<$fd>) {
		$cstuff="";
		chomp;

		# Very basic stuff at the moment...

		$l=0; $x=length;
		while($l<$x) {
			if(!$inquote) {
				$i=0;
				for $cc (@start_c) {
					if(substr($_,$l,length($cc)) eq $cc) {
						$inquote=1;
						$ctype=$i;
						$l+=length($cc);
						last;
					}
				}
				next if $inquote;
			}
			if($inquote) {
				$i=0;
				for $cc (@end_c) {
					next if $cc eq "";
					if(substr($_,$l,length($cc)) eq $cc) {
						$inquote=0;
						$ctype=0;
						$l+=length($cc);
						last;
					}
				}
				next if ! $inquote;
			}
			if(!$inquote) {
				$cstuff.=substr($_,$l,1);
			}
			$l++;
		}
		if($inquote && $end_c[$ctype] eq "") {
			$inquote=0;
		}
		$data.=$cstuff;
	}
	close($fd);
	return($data);
}

#########################################################################
# Default suffixes to ignore...						#
#########################################################################

@S=qw(sas7bdat sas7bvew sas7bndx sas7bcat log out backup swp);
push @S,"[0-9]+","[0-9\-_]+","tar","np","lst";

print "               Report for Directories $ARGV[0] / $ARGV[1]\n\n";
if(@ARG_onlysuffixes) {
	print "Using only suffixes: ",join(",",@ARG_onlysuffixes),"\n\n";
} else {
	print "Ignoring suffixes: ",join(",",@S),"\n\n";
}
if(defined($ARG_match)) {
	print "Matching files using pattern '$ARG_match'.\n";
}

$DEV_SRC=$ARGV[0];
$TEST_SRC=$ARGV[1];

if(! -d $TEST_SRC) {
	print "Unable to find directory $TEST_SRC\n"; exit 1;
}
if(! -d $DEV_SRC) {
	print "Unable to find directory $DEV_SRC\n"; exit 1;
}

# D is the list of directories from the 1st directory, so build list now.

sub ww {
my $x=$File::Find::name;

	return if ! -d $x;
	$x=substr($x,length($DEV_SRC));
	push @D,$x;
}

@D=();
find(\&ww,$DEV_SRC);

$in_first_only=0;
$in_second_only=0;
$different=0;

for $cdir (@D) {
	$cdir="." if $cdir eq "";
	%tfiles=();
	%dfiles=();
	if(! -d "$TEST_SRC/$cdir") {
		print "\nWARNING: Missing directory: $TEST_SRC/$cdir\n\n";
	} else {
		opendir(D,"$TEST_SRC/$cdir") or die("Can not open directory: $TEST_SRC/$cdir");
		while($x=readdir(D)) {
			$s=$x; $s =~ s/.*\.//;
			if($s ne "") {
				if(@ARG_onlysuffixes) {
					$ig=1;
					for my $cpat (@ARG_onlysuffixes) {
						if($s =~ /^$cpat$/) {
							$ig=0; last;
						}
					}
				} else {
					$ig=0;
					for (@S) {
						$cpat=$_;
						if($s =~ /^$cpat$/) {
							$ig=1; last;
						}
					}
				}
				next if $ig;
				if(defined($ARG_match) && $x !~ /$ARG_match/) {
					next;
				}
			}
			if(defined($ARG_pattern) && $x !~ /$ARG_pattern/) {
				next;
			}
			$tfiles{$x}=1 if ! -d "$TEST_SRC/$cdir/$x";
		}
		closedir(D);
	}

	if(! -d "$DEV_SRC/$cdir") {
		print "\nWARNING: Missing directory: $DEV_SRC/$cdir\n\n";
	} else {
		opendir(D,"$DEV_SRC/$cdir") or die("Can not open directory: $DEV_SRC/$cdir");
		while($x=readdir(D)) {
			$s=$x; $s =~ s/.*\.//;
			if($s ne "") {
				next if scalar(grep {/^$s$/} @S);
			}
			$dfiles{$x}=1 if ! -d "$DEV_SRC/$cdir/$x";
		}
		closedir(D);
	}

	#################################################################
	# Show files that exist only in dev and not in test...		#
	#################################################################

	$header=0;
	for $c (sort keys(%dfiles)) {
		$s=$c; $s =~ s/.*\.//;
		if($s ne "") {
			if(@ARG_onlysuffixes) {
				$ig=1;
				for my $cpat (@ARG_onlysuffixes) {
					if($s =~ /^$cpat$/) {
						$ig=0; last;
					}
				}
			} else {
				$ig=0;
				for (@S) {
					$cpat=$_;
					if($s =~ /^$cpat$/) {
						$ig=1; last;
					}
				}
			}
			next if $ig;
			if(defined($ARG_match) && $c !~ /$ARG_match/) {
				next;
			}
		}
		next if exists($tfiles{$c});
		if(!$header) {
			$x="Files in $DEV_SRC/$cdir, not in $TEST_SRC/$cdir:";
			$xl=length($x);
			$x2="=" x $xl;
			print "\n$x\n";
			print "$x2\n";
			$header=1;
		}
		print "$c\n";
		$in_first_only++;
	}
	
	#################################################################
	# Show files that exist only in test and not in dev...		#
	#################################################################

	$header=0;
	$SPC=" " x 39;
	for $c (sort keys(%tfiles)) {
		$s=$c; $s =~ s/.*\.//;
		if($s ne "") {
			if(@ARG_onlysuffixes) {
				$ig=1;
				for my $cpat (@ARG_onlysuffixes) {
					if($s =~ /^$cpat$/) {
						$ig=0; last;
					}
				}
			} else {
				$ig=0;
				for (@S) {
					$cpat=$_;
					if($s =~ /^$cpat$/) {
						$ig=1; last;
					}
				}
			}
			if(defined($ARG_match) && $c !~ /$ARG_match/) {
				next;
			}
			next if $ig;
		}
		next if exists($dfiles{$c});
		if(!$header) {
			$x="$SPC Files in $TEST_SRC/$cdir, not in $DEV_SRC/$cdir:"; $xl=length($x); $x2= "=" x $xl;
			print "\n$x\n";
			print "$SPC$x2\n";
			$header=1;
		}
		print "$SPC $c\n";
		$in_second_only++;
	}

	#################################################################
	# Now attempt to show any files that exist in both, but		#
	# are different.						#
	#################################################################

	$header=0;
	$SPC=" " x 19;
	for $c (sort keys(%tfiles)) {
		next if !exists($dfiles{$c});

		$F1d=Digest::MD5->new;
		$F2d=Digest::MD5->new;
		if(!scalar(@comments)) {
			open(F1,"$TEST_SRC/$cdir/$c") or die("Can not open file [$TEST_SRC/$cdir/$c]: $!");
			open(F2,"$DEV_SRC/$cdir/$c") or die("Can not open file [$DEV_SRC/$cdir/$c]: $!");
			$F1d->addfile(*F1);
			$F2d->addfile(*F2);
		} else {
			$f1=mangle_comments("$TEST_SRC/$cdir/$c");
			$f2=mangle_comments("$DEV_SRC/$cdir/$c");
			$F1d->add($f1);
			$F2d->add($f2);
		}
		if($F1d->hexdigest ne $F2d->hexdigest) {
			if(!$header) {
				$x="Files differ in $cdir:"; $xl=length($x); $x2="=" x $xl;
				print "\n$SPC$x\n";
				print "$SPC$x2\n";
				$header=1;
			}
			print "$SPC $c\n";
			$different++;
			if($ARG_diffs) {
				$cmd="diff $DEV_SRC/$cdir/$c $TEST_SRC/$cdir/$c";
				system($cmd);
			}
		}
	}
}

if(!$ARG_nosummary) {
	print "\n\n        Summary Information\n";
	print "        ===================\n";
	print "Files only under $DEV_SRC : $in_first_only\n";
	print "Files only under $TEST_SRC : $in_second_only\n";
	print "Total differing files : $different\n\n";
}

